package ru.evga.gwt.lib.common.client.request;

import ru.evga.gwt.lib.common.client.domain.BaseEntityDto;
import ru.evga.gwt.lib.common.client.domain.ResultContainer;
import ru.evga.gwt.lib.common.client.request.jsonrpc.IntegerJsonRpcResult;
import ru.evga.gwt.lib.common.client.request.jsonrpc.JsonRpcRequest;
import ru.evga.gwt.lib.common.client.request.jsonrpc.JsonRpcResult;
import ru.evga.gwt.lib.common.client.request.jsonrpc.RpcError;
import ru.evga.gwt.lib.common.client.request.params.FindEntityParams;
import ru.evga.gwt.lib.common.client.request.params.FindEntityRangeParams;
import ru.evga.gwt.lib.common.client.request.params.RemoveEntityParams;
import ru.evga.gwt.lib.common.client.request.params.SaveEntityParams;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.user.client.Random;


public class RequestFactory
{
//    private final static int DEFAULT_TIMEOUT_MILLIS = 10000; //10sec
    private final static int DEFAULT_TIMEOUT_MILLIS = 100000; //100sec
    private static String SERVICE_URL_PREFIX = GWT.getHostPageBaseURL() + "jsonrpc/";
    private static String SERVICE_URL_SUFFIX = "Service";
    private static int timeoutMillis = DEFAULT_TIMEOUT_MILLIS;
    
    
    private static final String REMOVE_METHOD = "remove";
    private static final String COUNT_METHOD = "count";
    private static final String SAVE_METHOD = "save";
    private static final String FIND_METHOD = "find";
    private static final String FIND_RANGE_METHOD = "findRange";

    static public void setDefaultTimeoutMillis(int timeoutMillis)
    {
        RequestFactory.timeoutMillis = timeoutMillis;
    }

    static public int getDefaultTimeoutMillis()
    {
        return RequestFactory.timeoutMillis;
    }

    /**
     * 
     * @author evga
     *
     * @param <T>
     */
    private interface ServiceRequestCallback<T>
    {
        void ok(T data);

        void error(String data);
    }

    private static int generateId()
    {
        return Random.nextInt();
    }

    private static <T extends JavaScriptObject> JsonRpcRequest<T> makeJsonRpcRequest(T serviceParams)
    {
        JsonRpcRequest<T> jsonRpcRequest = JsonRpcRequest.createEmptyParams();
        jsonRpcRequest.setId(generateId());
        jsonRpcRequest.setParams(serviceParams);
        return jsonRpcRequest;
    }

    private static JsonRpcRequest<?> makeJsonRpcRequest()
    {
        JsonRpcRequest<JavaScriptObject> jsonRpcRequest = JsonRpcRequest.createEmptyParams();
        jsonRpcRequest.setId(generateId());
        return jsonRpcRequest;
    }


    static private RequestBuilder cretaeRequestBuilder(String serviceName, String requestData)
    {
        RequestBuilder builder = new RequestBuilder(RequestBuilder.POST, SERVICE_URL_PREFIX
            + serviceName + SERVICE_URL_SUFFIX);
        builder.setRequestData(requestData);
        builder.setTimeoutMillis(timeoutMillis);
        return builder;
    }
    
    static private <T> void sendRequestForObject(String serviceName, String requestData,
        final ServiceRequestCallback<T> requestCallback, int timeoutMillis)
    {
        RequestBuilder builder = cretaeRequestBuilder(serviceName, requestData);
        RequestCallback rc = new RequestCallback()
        {
            public void onResponseReceived(Request request, Response response)
            {
                try
                {
                    String json = response.getText();
                    JsonRpcResult<T> jsonRpcResult = JsonRpcResult.asJsonRpcResult(json);
                    RpcError error = jsonRpcResult.getError();
                    if (error != null && error.getCode() != 0)
                    {
                        requestCallback.error(error.getMessage());
                    }
                    else
                    {
                        T data = jsonRpcResult.getResult();
                        requestCallback.ok(data);
                    }
                }
                catch (Exception e) 
                {
                    requestCallback.error(e.getLocalizedMessage());
                }
            }

            public void onError(Request request, Throwable exception)
            {
                requestCallback.error(exception.getLocalizedMessage());
            }
        };
        builder.setCallback(rc);
        try
        {
            builder.send();
        }
        catch (RequestException e)
        {
            requestCallback.error(e.getLocalizedMessage());
        }
    }

    static private void sendRequestForInteger(String serviceName, String requestData,
        final ServiceRequestCallback<Integer> requestCallback, int timeoutMillis)
    {
        RequestBuilder builder = cretaeRequestBuilder(serviceName, requestData);
        RequestCallback rc = new RequestCallback()
        {
            public void onResponseReceived(Request request, Response response)
            {
                String json = response.getText();
                IntegerJsonRpcResult jsonRpcResult = IntegerJsonRpcResult.asJsonRpcResult(json);
                RpcError error = jsonRpcResult.getError();
                if (error != null && error.getCode() != 0)
                {
                    requestCallback.error(error.getMessage());
                }
                else
                {
                    Integer data = jsonRpcResult.getResult();
                    requestCallback.ok(data);
                }
            }

            public void onError(Request request, Throwable exception)
            {
                requestCallback.error(exception.getLocalizedMessage());
            }
        };
        builder.setCallback(rc);
        try
        {
            builder.send();
        }
        catch (RequestException e)
        {
            requestCallback.error(e.getLocalizedMessage());
        }
    }

    
    
    public static <E extends BaseEntityDto> void findEntityRange(FindEntityRangeParams params,
        final IServiceRequestCallback<ResultContainer<E>, String> callback)
    {
        JsonRpcRequest<FindEntityRangeParams> jsonRpcRequest = makeJsonRpcRequest(params);
        jsonRpcRequest.setMethod(FIND_RANGE_METHOD);
        String jsonRpcRequestString = new JSONObject(jsonRpcRequest).toString();
        RequestFactory.sendRequestForObject(params.getEntityName(), jsonRpcRequestString,
            new ServiceRequestCallback<ResultContainer<E>>()
            {
                public void ok(ResultContainer<E> data)
                {
                    callback.ok(data);
                }

                public void error(String data)
                {
                    callback.error(data);
                }
            }, getDefaultTimeoutMillis());
    }

    public static <E extends BaseEntityDto> void findEntity(FindEntityParams params,
        final IServiceRequestCallback<E, String> callback)
    {
        JsonRpcRequest<FindEntityParams> jsonRpcRequest = makeJsonRpcRequest(params);
        jsonRpcRequest.setMethod(FIND_METHOD);
        String jsonRpcRequestString = new JSONObject(jsonRpcRequest).toString();
        RequestFactory.sendRequestForObject(params.getEntityName(), jsonRpcRequestString,
            new ServiceRequestCallback<E>()
            {
                public void ok(E data)
                {
                    callback.ok(data);
                }

                public void error(String data)
                {
                    callback.error(data);
                }
            }, getDefaultTimeoutMillis());
    }

    public static <E extends BaseEntityDto> void saveEntity(SaveEntityParams<E> params,
        final IServiceRequestCallback<E, String> callback)
    {
        E entity = params.getEntity();
        JsonRpcRequest<E> jsonRpcRequest = makeJsonRpcRequest(entity);
        jsonRpcRequest.setMethod(SAVE_METHOD);
        String jsonRpcRequestString = new JSONObject(jsonRpcRequest).toString();
        RequestFactory.sendRequestForObject(params.getEntityName(), jsonRpcRequestString,
            new ServiceRequestCallback<E>()
            {
                public void ok(E data)
                {
                    callback.ok(data);
                }

                public void error(String data)
                {
                    callback.error(data);
                }
            }, getDefaultTimeoutMillis());
    }

    public static void removeEntity(RemoveEntityParams params,
        final IServiceRequestCallback<Void, String> callback)
    {
        JsonRpcRequest<RemoveEntityParams> jsonRpcRequest = makeJsonRpcRequest(params);
        jsonRpcRequest.setMethod(REMOVE_METHOD);
        String jsonRpcRequestString = new JSONObject(jsonRpcRequest).toString();
        RequestFactory.sendRequestForObject(params.getEntityName(), jsonRpcRequestString,
            new ServiceRequestCallback<Void>()
            {
                public void ok(Void data)
                {
                    callback.ok(data);
                }

                public void error(String data)
                {
                    callback.error(data);
                }
            }, getDefaultTimeoutMillis());
    }

    
    public static void countEntity(String entityName,
        final IServiceRequestCallback<Integer, String> callback)
    {
        JsonRpcRequest<?> jsonRpcRequest = makeJsonRpcRequest();
        jsonRpcRequest.setMethod(COUNT_METHOD);
        String jsonRpcRequestString = new JSONObject(jsonRpcRequest).toString();
        RequestFactory.sendRequestForInteger(entityName, jsonRpcRequestString,
            new ServiceRequestCallback<Integer>()
            {
                public void ok(Integer data)
                {
                    callback.ok(data);
                }

                public void error(String data)
                {
                    callback.error(data);
                }
            }, getDefaultTimeoutMillis());
    }
    
    
    
}
